#include <assert.h>
#include <map>
#include <vector>
#include <time.h>
#include <GL/glut.h>
#include <iostream>
#include "SceneManager.hpp"
#include "Entities/BaseEntity.hpp"
#include "ScreenSystem/ScreenManager.hpp"
#include "ScreenSystem/DemoSceneScreen.hpp"
#include "HDRDemoConstants.hpp"
#include "Camera/CameraManager.hpp"
#include "Camera/CameraObject.hpp"
#include "Mathematics/MMatrix4.hpp"

//! debug
#include "ResourceManagers/TextureManager.hpp"
#include "ResourceManagers/TextureObject.hpp"
#include "Globals.hpp"

//! Setting values for static variables.
SceneManager*            SceneManager::m_instance = 0;


//! GetInstance method returns pointer to SceneManager instance
//! and ensures that only one such instance exists.
SceneManager* SceneManager::GetInstance()
{
	if( !m_instance )
	{
		m_instance = new SceneManager;
	}

	return m_instance;
}

SceneManager::SceneManager()
{
	m_startTick     = clock();
	m_lastFrameTick = m_startTick;
	m_idIndex       = 0;
	m_numOfFrames   = 0;
}

void SceneManager::drawEntities()
{
	map<int, BaseEntity*>::iterator mapBegin = GetInstance()->m_entitiesMap.begin();
	map<int, BaseEntity*>::iterator mapEnd   = GetInstance()->m_entitiesMap.end();

	mapBegin = GetInstance()->m_entitiesMap.begin();
	for( mapBegin; mapBegin != mapEnd; ++mapBegin )
		mapBegin->second->Render();
}

//! Method registering entity with its unique ID number.
void SceneManager::RegisterEntity( BaseEntity* newEntity )
{
	// Check if entity exists.
	assert( newEntity );

	// Register entity with unique ID.
	newEntity->SetEntityID( GenerateUniqueID() );
	//m_entitiesMap.insert( std::make_pair( newEntity->GetEntityID(), newEntity ) );

	// Mateusz zaproponowal prostsze konstrukcyjnie rozwiazanie
	m_entitiesMap[newEntity->GetEntityID()] = newEntity;
}

//! Here all entities are unregistered and destroyed.
void SceneManager::UnregisterAllEntities()
{
	map<int, BaseEntity*>::iterator mapBegin = m_entitiesMap.begin();
	map<int, BaseEntity*>::iterator mapEnd   = m_entitiesMap.end();

	for( mapBegin; mapBegin != mapEnd; ++mapBegin )
	{
		m_entitiesToDestroy.push_back( mapBegin->first );
	}



	vector<int>::iterator beginOfUnregistered = m_entitiesToDestroy.begin();
	vector<int>::iterator endOfUnregistered   = m_entitiesToDestroy.end();

	// Destroy unregistered entities.
	for( beginOfUnregistered; beginOfUnregistered != endOfUnregistered; ++ beginOfUnregistered )
	{
		delete m_entitiesMap[ *beginOfUnregistered ];
		m_entitiesMap[ *beginOfUnregistered ] = NULL;
		//m_entitiesMap.erase( *beginOfUnregistered );
	}
}

//! Unregistered entity will be destroyed on next UpdateScene() call.
void SceneManager::UnregisterEntity(int entityID)
{
	m_entitiesToDestroy.push_back( entityID );
}

//! Method updating whole scene. First step is to destroy unregistered entities.
//! Second is to update state of registered entities. Third step is to render all
//! registered entities.
void SceneManager::UpdateScene( clock_t tick )
{
	++m_numOfFrames;
	clock_t delta = tick - m_lastFrameTick;
	m_lastFrameTick = tick;

	if( delta <= 0 )
		return;

	if( !((tick - m_startTick) % 2000) )
	{
		std::cout << "FPS based on delta: " << 1000.0/delta << std::endl;
		std::cout << "delta: " << delta << std::endl;
		//std::cout << "FPS average:        " << m_numOfFrames/((tick - m_startTick)) << std::endl;
	}

	map<int, BaseEntity*>::iterator mapBegin = GetInstance()->m_entitiesMap.begin();
	map<int, BaseEntity*>::iterator mapEnd   = GetInstance()->m_entitiesMap.end();

	vector<int>::iterator beginOfUnregistered = GetInstance()->m_entitiesToDestroy.begin();
	vector<int>::iterator endOfUnregistered   = GetInstance()->m_entitiesToDestroy.end();

	// Destroy unregistered entities.
	for( beginOfUnregistered; beginOfUnregistered != endOfUnregistered; ++ beginOfUnregistered )
	{
		delete GetInstance()->m_entitiesMap[ *beginOfUnregistered ];
		GetInstance()->m_entitiesMap[ *beginOfUnregistered ] = NULL;

		//m_entitiesMap.erase( *beginOfUnregistered );
	}

	GetInstance()->m_entitiesToDestroy.clear();


	// Update all registered entities.
	for( mapBegin; mapBegin != mapEnd; ++mapBegin )
		mapBegin->second->Update( clock() );



/////////////////////////////////////////////////////////
//! RENDERING PART GOES HERE ////////////////////////////
/////////////////////////////////////////////////////////

	//glCullFace(GL_BACK);
	//glEnable(GL_CULL_FACE);

	glClearColor( 0.0, 0.0, 0.0, 1.0 );
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

	glMatrixMode( GL_PROJECTION );
	glLoadMatrixd( Globals::GetInstance()->GetLightMatrix( PROJECTION_MATRIX ) );

	glMatrixMode( GL_MODELVIEW );
	glLoadMatrixd( Globals::GetInstance()->GetLightMatrix( MODELVIEW_MATRIX ) );

	dynamic_cast<ShadowMapFBO*>(ScreenManager::GetInstance()->GetCurrentScreen())->computeShadowMap(4, 4);
	//((ShadowMapFBO*)(ScreenManager::GetInstance()->GetCurrentScreen()))->renderToFBO( true );

	//glBindTexture( GL_TEXTURE_2D, *(Globals::GetInstance()->GetSpecialTexture( LIGHT_POSITIONS )) );

	//glEnable( GL_POLYGON_OFFSET_FILL );
	//glPolygonOffset(0.0, 1.0);

	//ExtensionsLoader::GetInstance()->glUseProgramObjectARB( ExtensionsLoader::GetInstance()->GetShader( DEPTH_MAP_GENERATOR ) );

	//mvMatricesVec mvec = Globals::GetInstance()->GetLookAtMaticesVector();
	//mvMatricesVec::iterator mat_it = mvec.begin();
	//int size = mvec.size();

	//glMatrixMode( GL_MODELVIEW );
	//int shadcount=0;
	//MMatrix4 matrix;

	//glCullFace(GL_FRONT);
	//glEnable(GL_CULL_FACE);
	//for( int i = 0; i < 4096; i+=4096 )
	//{
	//	for( int j = 0; j < 4096; j+=4096 )
	//	{
	//		matrix = (*mat_it)->GetModelViewMatrix();
	//		Globals::GetInstance()->SetLightMatrix( MODELVIEW_MATRIX, matrix );
	//		glLoadMatrixd( Globals::GetInstance()->GetLightMatrix( MODELVIEW_MATRIX) );
	//		++shadcount;
	//		glViewport(i,j, 4096, 4096 );

	//		mapBegin = GetInstance()->m_entitiesMap.begin();
	//	
	//		for( mapBegin; mapBegin != mapEnd; ++mapBegin )
	//			mapBegin->second->Render();

	//		++mat_it;
	//	}
	//}
	//cout << "shadcount: " << shadcount << endl;
	//glDisable(GL_CULL_FACE);
	//glDisable(GL_POLYGON_OFFSET_FILL);

	//glCullFace(GL_BACK);

	
	//((ShadowMapFBO*)(ScreenManager::GetInstance()->GetCurrentScreen()))->renderToFBO( false );
	//glDisable( GL_POLYGON_OFFSET_FILL );

	//glMatrixMode( GL_TEXTURE );
	//glLoadIdentity();
	//glTranslatef( 0.5f, 0.5f, 0.5f );
	//glScalef( 0.5f, 0.5f, 0.5f );

	//glMultMatrixd( Globals::GetInstance()->GetLightMatrix( PROJECTION_MATRIX ) );

	//moved after computerShadow() was added
	mvMatricesVec mvec = Globals::GetInstance()->GetLookAtMaticesVector();
	mvMatricesVec::iterator mat_it = mvec.begin();
	MMatrix4 matrix;

    mat_it = mvec.begin();
	matrix = (*mat_it)->GetModelViewMatrix();
	Globals::GetInstance()->SetLightMatrix( MODELVIEW_MATRIX, matrix );
	glMultMatrixd( Globals::GetInstance()->GetLightMatrix( MODELVIEW_MATRIX ) );

	MMatrix4* invMatrix = new MMatrix4;
	GLdouble* camModelview; camModelview = Globals::GetInstance()->GetCameraMatrix( MODELVIEW_MATRIX );
	invMatrix->InverseMatrix( MMatrix4( camModelview[0], camModelview[1], camModelview[2], camModelview[3], camModelview[4],
		camModelview[5], camModelview[6], camModelview[7], camModelview[8], camModelview[9], camModelview[10], camModelview[11],
		camModelview[12], camModelview[13], camModelview[14], camModelview[15]) );

	//glMultMatrixf( invMatrix->m_matrix );


	glMatrixMode( GL_PROJECTION );
	glLoadMatrixd( Globals::GetInstance()->GetCameraMatrix( PROJECTION_MATRIX ));
	//glLoadMatrixd( Globals::GetInstance()->GetLightMatrix( PROJECTION_MATRIX ));
	glMatrixMode( GL_MODELVIEW );
	glLoadMatrixd( Globals::GetInstance()->GetCameraMatrix( MODELVIEW_MATRIX ));
	//glLoadMatrixd( Globals::GetInstance()->GetLightMatrix( MODELVIEW_MATRIX ));


	ExtensionsLoader::GetInstance()->glUseProgramObjectARB( ExtensionsLoader::GetInstance()->GetShader( SHADOW_TEST ) );
	glBindTexture( GL_TEXTURE_2D, dynamic_cast<ShadowMapFBO*>(ScreenManager::GetInstance()->GetCurrentScreen())->getShadowMapID());//*(Globals::GetInstance()->GetSpecialTexture( SHADOW_MAP )) );
	ExtensionsLoader::glActiveTextureARB(GL_TEXTURE1_ARB);
	glBindTexture( GL_TEXTURE_2D, *(Globals::GetInstance()->GetSpecialTexture( LIGHT_POSITIONS)) );
	ExtensionsLoader::glActiveTextureARB(GL_TEXTURE0_ARB);


	ExtensionsLoader::glUniformMatrix4fvARB( *(ExtensionsLoader::GetInstance()->GetMVInvLoc()), 1, GL_FALSE, invMatrix->m_matrix );	

	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE_ARB);
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL );
	//ExtensionsLoader::GetInstance()->glActiveTextureARB( GL_TEXTURE1 );
	//ExtensionsLoader::GetInstance()->glUseProgramObjectARB( ExtensionsLoader::GetInstance()->GetShader( SHADOW_TEST ) );
	//glBindTexture( GL_TEXTURE_2D, *((Globals::GetInstance()->GetSpecialTexture( LIGHT_POSITIONS ))) );
	//ExtensionsLoader::GetInstance()->glActiveTextureARB( GL_TEXTURE0 );


	mapBegin = GetInstance()->m_entitiesMap.begin();
	
	for( mapBegin; mapBegin != mapEnd; ++mapBegin )
		mapBegin->second->Render();

	GLfloat MaterialDiffuse[4],
		    MaterialSpecular[4];

	float   MaterialShininess[1];

	MaterialDiffuse[0] = 1.0;
	MaterialDiffuse[1] = 1.0;
	MaterialDiffuse[2] = 1.0;
	MaterialDiffuse[3] = 1.0f;
	MaterialSpecular[0] = 1.0;
	MaterialSpecular[1] = 1.0;
	MaterialSpecular[2] = 1.0;
	MaterialSpecular[3] = 1.0f;
	MaterialShininess[0] = 99.0f;
	glMaterialfv( GL_FRONT, GL_DIFFUSE, MaterialDiffuse );
	glMaterialfv( GL_FRONT, GL_SPECULAR, MaterialSpecular );
	glMaterialfv( GL_FRONT, GL_SHININESS, MaterialShininess );

	//glDisable(GL_CULL_FACE);

	glNormal3f( 0.0, 1.0, 0.0 );
	glBegin( GL_QUADS );
		glVertex3f( -10.0f, 0.0f, -10.0f );
		glVertex3f( -10.0f, 0.0f, 10.0f );
		glVertex3f( 10.0f, 0.0f, 10.0f );
		glVertex3f( 10.0f, 0.0f, -10.0f );
	glEnd();

	//glEnable(GL_CULL_FACE);

//! QUAD TEXTURED WITH DEPTH MAP
	//ExtensionsLoader::glUseProgramObjectARB( 0 );
	//ExtensionsLoader::GetInstance()->glActiveTextureARB( GL_TEXTURE0 );
	//glDisable( GL_LIGHTING );
	//glEnable( GL_TEXTURE_2D );
	////glDisable (GL_COLOR_MATERIAL );
	//glBindTexture( GL_TEXTURE_2D, *(Globals::GetInstance()->GetSpecialTexture( SHADOW_MAP )) );

	//glBegin( GL_QUADS );
	//	glTexCoord2f( 1.0f, 1.0f ); glVertex3f( -5.0f, 0.0f, -5.0f );
	//	glTexCoord2f( 1.0f, 0.0f ); glVertex3f( -5.0f, 0.0f, 5.0f );
	//	glTexCoord2f( 0.0f, 0.0f ); glVertex3f( 5.0f, 0.0f, 5.0f );
	//	glTexCoord2f( 0.0f, 1.0f ); glVertex3f( 5.0f, 0.0f, -5.0f );
	//glEnd();

	//glColor3f( 1.0, 1.0, 1.0 );
	//glBegin( GL_QUADS );
	//	glTexCoord2f( 1.0f, 1.0f ); glVertex3f( -10.0f, -5.0f, -10.0f );
	//	glTexCoord2f( 1.0f, 0.0f ); glVertex3f( -10.0f, -5.0f, 10.0f );
	//	glTexCoord2f( 0.0f, 0.0f ); glVertex3f( 10.0f, -5.0f, 10.0f );
	//	glTexCoord2f( 0.0f, 1.0f ); glVertex3f( 10.0f, -5.0f, -10.0f );
	//glEnd();

//!------------------------VIEWPORT-WITH-SHADOW-MAP-VISUALIZED--------------------------------------

	/*int vp_width, vp_height;
	vp_width = 256;
	vp_height = 256;

	ExtensionsLoader::GetInstance()->glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
	ExtensionsLoader::GetInstance()->glUseProgramObjectARB(0);

	glPushAttrib (GL_VIEWPORT_BIT);
	glViewport(0, 0, vp_width, vp_height);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluOrtho2D(0, vp_width, vp_height, 0);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	glClear(GL_DEPTH_BUFFER_BIT);

	glEnable(GL_TEXTURE_2D);
	//TextureObject* pTex = reinterpret_cast<TextureObject*>(TextureManager::GetInstance()->GetTexture("grass.bmp"));
	//glBindTexture( GL_TEXTURE_2D, pTex->GetTextureID());
	GLuint tid = dynamic_cast<ShadowMapFBO*>(ScreenManager::GetInstance()->GetCurrentScreen())->getShadowMapID();
	glBindTexture( GL_TEXTURE_2D, tid);
	glBegin(GL_QUADS);
	glTexCoord2f(0.0f, 0.0f); glVertex2i(0, 0);
	glTexCoord2f(1.0f, 0.0f); glVertex2i(vp_width, 0);
	glTexCoord2f(1.0f, 1.0f); glVertex2i(vp_width, vp_height);	
	glTexCoord2f(0.0f, 1.0f); glVertex2i(0, vp_height);
	
	glEnd();

	glPopAttrib();*/

//!--------------------------------------------------------------------------------------------------

	glutSwapBuffers();
}